<?php
/**
 * http://code.google.com/p/php-iterator-utils/
 * @license http://www.apache.org/licenses/LICENSE-2.0.html
 * @author Seva Lapsha (seva.lapsha@gmail.com)
 */
namespace lapsha\iterators\composite {
	include_once __DIR__.'/common.php';
	use \lapsha\iterators\common\Wrapper;
	
	/**
	 * Split Iterator into Iterator of Iterators of given size
	 * @see array_chunk
	 */
	class Chunker implements \Iterator {
		/** @var \Iterator */
		protected $i;
		/** @var \Iterator */
		protected $current;
		protected $key;
		/**
		 * @param \Iterator $i
		 * @param int $size
		 */
		function __construct(\Iterator $i, $size) {
			if ($size < 1)
				throw new \InvalidArgumentException('Invalid chunk size: '.$size);
			$this->i = $i;
			$this->size = $size;
		}
		function valid() {
			return $this->i->valid();
		}
		/** @return _Chunk */
		protected function nextChunk() {
			return $this->current = new _Chunk($this->i, $this->size);
		}
		function rewind() {
			$rewinded = $this->i->rewind();
			$this->nextChunk();
			$this->key = 0;
			return $rewinded;
		}
		function current() {
			return $this->current;
		}
		function next() {
			while($this->current->valid())
				$this->current->next();
			++$this->key;
			return $this->nextChunk();
		}
		function key() {
			return $this->key();
		}
	}

	class _Chunk extends Wrapper {
		protected $size;
		protected $count = 0;
		/**
		 * @param Iterator $i
		 * @param int $size
		 */
		function __construct(\Iterator $i, $size) {
			parent::__construct($i);
			$this->size = $size;
		}
		function next() {
			if (++$this->count > $this->size)
				return;
			return parent::next();
		}
		function valid() {
			return !($this->count >= $this->size) && $this->i->valid();
		}
		function rewind() {
			if ($this->count > 0)
				trigger_error("Chunk iterator cannot be rewinded", \E_USER_WARNING);
		}
	}

	/**
	 * Merge Iterator of Iterators into a single Iterator
	 * @see array_merge
	 */
	class Merger extends Wrapper {

		function valid() {
			return $this->i->current()->valid() || $this->i->valid();
		}
		function rewind() {
			$this->i->rewind();
			return $this->i->current()->rewind();
		}
		function current() {
			return $this->i->current()->current();
		}
		function next() {
			do {
				$next = $this->i->current()->next();
				if ($this->i->current()->valid())
					return $next;
				$this->i->next();
				$this->i->current()->rewind();
				if ($this->i->current()->valid())
					return $this->i->current()->current();
			} while ($this->i->valid());
		}
		function key() {
			return $this->i->current()->key();
		}
	}

	/**
	 * Reduce iterator to a single value
	 * @see array_reduce
	 * @param \Iterator $i
	 * @param callback $callback to take 2 values for reduction and return single value
	 * @param $initial
	 */
	function reduce(\Iterator $i, $callback, $initial = null) {
		common\checkCallable($callback);
		$current = $initial;
		foreach($i as $next)
			$current = $callback($current, $next);
		return $current;
	}

	/**
	 * Sum array values
	 * @see array_sum
	 * @param \Iterator $i
	 */
	function sum(\Iterator $i) {
		return parent::__invoke($i, function(&$current, $next) {$current += $next;}, 0);
	}
}